/**
*
* @file asn1.c
*
* @brief Encode/Decode a message using Binary Encoding Rules (BER)
*
* This module, together with tlv.c, assists encoding data using ASN.1 BER.
*
* Encoding is done by calling encoding functions for each data item.
* An option allows coding integers with specific lengths, which, in general,
* is non-BER-compliant (BER requires minimum length integer encoding).
*
* The calling function is responsible for determining item tags, including
* the Class and Constructed bits.  Usually functions in the TLV module
* must be used, in addition to the functions here, to allow general
* BER encoding.
*
* Tag values:
* There are four tag classes: Universal, Application, Context-specific,
* and Private.  Most tags will be either Universal or Context-specific.
* Which class is used depends on the way that the message is defined
* in ASN.1.  Tags defined with explicit values are not Universal.
*
* The value of Universal tags is specified by the ITU standard.  Most
* functions defined in this module encode with Universal tags.
*
* General Usage:
* The caller defines an output buffer to contain the generated message.
* The output functions take two pointers, result and resultLength.
*
* result - points to a pointer to the next output buffer location to be
*          written.
* resultLength - points to the number of unused locations in the output
*          buffer.  It should initially be set to the maximum length of
*          the output message.
*
* The message is encoded by calling the encoding functions in the order
* that the data items are added to the message.  The result and resultLength
* pointers are passed to each encoding function, which advances *result
* and reduces *resultLength for each byte added to the output buffer.
*
* Encoding Nested TLVs:
*
* An ASN.1 SEQUENCE, SEQUENCE OF, SET, or SET OF type wraps other
* ASN.1 types.  Since the length of a SEQUENCE item encompasses all
* items which follows the SEQUENCE tag and items are coded in order,
* the length of the SEQUENCE is generally unknown when the tag and length
* fields are encoded.  Three functions are provided:
*      asn1_openTag() - called to start a wrapping tag
*      asn1_closeTag() - called to terminate a wrapping tag with 3-byte length.
*      asn1_closeTag_DER() - called to terminate a wrapping tag with DER
*                        encoding of length (1 to 3 bytes).
* These routines ensure that a valid length is set.
*
* Exceptions:
* Calling these functions may result in exceptions being thrown:
*
* TLV_INSUFFICIENT_BUFFER - insufficient buffer length for the item
*              being added.
* TLV_INVALID_SETLENGTH - item length and forcedLengthSize are incompatible
* TLV_MALFORMED - for message decoding functions:
*          tag was greater than two bytes
*          TLV length went beyond the end of the input buffer
*          TLV length incorrect for data byte (boolean)
*          the length of data exceeds the size of the receiving item,
*              such as specific integer length
*          integer data is negative for unsigned integer receiving item
*          BER indefinite length is used (not supported)
*
* Reference: ITU-T X.690 ASN.1 encoding rules: BER, CER, DER.
*
* Copyright ?2011 HID Corporation.  All rights reserved.
*
* This software is protected by copyright law and international treaties.
* Any unauthorized reproduction, distribution or use of the software is prohibited.
*
*/

#include <stdio.h>
#include <string.h>
#include <stddef.h>

#include "exception.h"
#include "utils.h"
#include "asn1.h"
#include "tlv.h"


/// <summary>
/// Encode BER Universal NULL type.
/// </summary>
///     After calling: A BER Universal NULL TLV has been added to the output
/// <param name="result">[in/out] input: pointer to pointer to next output location,
///                               output: point to pointer to next output location
///                               after the newly created TLV</param>
/// <param name="resultLength">[in/out] input: pointer to maximum number of remaining message bytes,
///                               output: pointer to maximum number of remaining message bytes after adding TLV</param>
void asn1_buildNull(uint8_t** result, uint16_t* resultLength)
{
    utils_setU2(*result, 0x0500);
    (*result) += sizeof(uint16_t);
    (*resultLength) -= sizeof(uint16_t);  
}

/// <summary>
/// Encode BER Universal OCTET STRING type.
/// </summary>
///     After calling: A BER Universal OCTET STRING TLV has been added to the output
/// <param name="data"> [in] pointer to data field to be added</param>
/// <param name="length"> length of data (bytes)</param>
/// <param name="result">[in/out] input: pointer to pointer to next output location,
///                               output: point to pointer to next output location
///                               after the newly created TLV</param>
/// <param name="resultLength">[in/out] input: pointer to maximum number of remaining message bytes in message buffer,
///                               output: pointer to maximum number of remaining message bytes  in message buffer after adding TLV</param>
void asn1_buildOctectString(uint8_t* Data, uint16_t length, uint8_t** result, uint16_t* resultLength)
{
    tlv_create(ASN1_TAG_OCTETSTRING, Data, length, result, resultLength);
}

/// <summary>
/// Encode BER Universal OID type.
///
/// Note: Object Identifiers start with the encoding for an organization
///       that issues name spaces for OIDs, thus ensuring uniqueness for all OIDs.
///       Encoding of the subidentifiers within the OID must be done before calling
///       this function.  asn1_buildOIDValue() can be used to code subidentifiers,
///       except for the special-case initial OID subidentifier.
/// </summary>
///     After calling: A BER Universal OID has been added to the output
/// <param name="oid"> [in] pointer to array of bytes containing the BER_encoded data field for an object identifier</param>
/// <param name="oidLength"> length of oid (bytes)</param>
/// <param name="result">[in/out] input: pointer to pointer to next output location,
///                               output: point to pointer to next output location
///                               after the newly created TLV</param>
/// <param name="resultLength">[in/out] input: pointer to maximum number of remaining message bytes,
///                               output: pointer to maximum number of remaining message bytes after adding TLV</param>
void asn1_buildOid(uint8_t* oid, uint16_t oidLength, uint8_t** result, uint16_t* resultLength)
{
    tlv_create(ASN1_TAG_OID, oid, oidLength, result, resultLength);    
}

/// <summary>
/// Append a data field to a message.
///
/// This function should be called after encoding the tag and length to a message.
/// It completes the TLV encoding for one item.
/// </summary>
///     After calling: The specified bytes have been added to the output.
/// <param name="data"> [in] pointer to array of bytes to be added</param>
/// <param name="dataLength"> length of data (bytes)</param>
/// <param name="result">[in/out] input: pointer to pointer to next output location,
///                               output: point to pointer to next output location
///                               after the newly created TLV</param>
/// <param name="resultLength">[in/out] input: pointer to maximum number of remaining message bytes,
///                               output: pointer to maximum number of remaining message bytes after adding TLV</param>
void asn1_append(uint8_t* Data, uint16_t dataLength, uint8_t** result, uint16_t* resultLength)
{
	tlv_setData(Data, dataLength, result, resultLength);    
}
 
/// <summary>
/// Encode an integer 0..7f as BER Universal integer type.
/// </summary>
///     After calling: A BER Universal integer has been added to the output
/// <param name="value"> value to encoded, 0..0x7f</param>
/// <param name="result">[in/out] input: pointer to pointer to next output location,
///                               output: point to pointer to next output location
///                               after the newly created TLV</param>
/// <param name="resultLength">[in/out] input: pointer to maximum number of remaining message bytes,
///                               output: pointer to maximum number of remaining message bytes after adding TLV</param>
void asn1_buildU1Integer(uint8_t value, uint8_t** result, uint16_t* resultLength)
{
    //ASSERT(value <= 0x7f);
	tlv_create(ASN1_TAG_INTEGER, &value, sizeof(value), result, resultLength);    
}

/// <summary>
/// Encode an integer 0..7fff as BER Universal integer type.
/// </summary>
///     After calling:  A BER Universal integer has been added to the output
/// <param name="value"> value to encoded, 0..0x7fff</param>
/// <param name="result">[in/out] input: pointer to pointer to next output location,
///                               output: point to pointer to next output location
///                               after the newly created TLV</param>
/// <param name="resultLength">[in/out] input: pointer to maximum number of remaining message bytes,
///                               output: pointer to maximum number of remaining message bytes after adding TLV</param>
void asn1_buildU2Integer(uint16_t value, uint8_t** result, uint16_t* resultLength)
{
    //ASSERT(value <= 0x7fff);
	if (value < 0x80) {
		asn1_buildU1Integer((uint8_t)value, result, resultLength);
	} else {
	    uint8_t buffer[sizeof(value)];
    	utils_setU2(buffer, value);		
	    tlv_create(ASN1_TAG_INTEGER, (uint8_t*)buffer, sizeof(buffer), result, resultLength);    
	}
}
 
/// <summary>
/// Encode an integer as BER Universal integer type.
///
/// Note: If USE_BERCOMPLIANT_INTEGER_ENCODING is defined, the resulting encoded data length
///       is 1 to 5 bytes.
///       If USE_BERCOMPLIANT_INTEGER_ENCODING is undefined, the resulting 1, 2, or 4 bytes,
///       with values >= 0x80000000 encoded as the negative value (value-(1<<32)).
/// </summary>
///     After calling: A BER Universal integer with given value has been added to the output
/// <param name="value"> value to encoded</param>
/// <param name="result">[in/out] input: pointer to pointer to next output location,
///                               output: point to pointer to next output location
//                                after the newly created TLV</param>
/// <param name="resultLength">[in/out] input: pointer to maximum number of remaining message bytes,
///                               output: pointer to maximum number of remaining message bytes after adding TLV</param>
void asn1_buildU4Integer(uint32_t value, uint8_t** result, uint16_t* resultLength)
{
	if (value < 0x8000) {
		asn1_buildU2Integer((uint16_t)value, result, resultLength);		
	} else {
        uint8_t buffer[5];        
    	
        utils_setU4(buffer + 1, value);

        if (value < 0x800000) {
            tlv_create(ASN1_TAG_INTEGER, (uint8_t*)(buffer + 2), 3, result, resultLength);
        }  
        else if (value < 0x80000000) {            
            tlv_create(ASN1_TAG_INTEGER, (uint8_t*)(buffer + 1), 4, result, resultLength);
        } 
        else {
            buffer[0] = 0;
            tlv_create(ASN1_TAG_INTEGER, (uint8_t*)(buffer + 0), 5, result, resultLength);
        }
	}
}

/// <summary>
/// Encode an arbitrary length integer with BER Universal integer tag.
///
/// The high-order bit of value[0] is the sign of the integer, with the value
/// expressed in two's complement.  For BER-compliant coding, the first 9 bits
/// must not be all zeros or all ones (minimum encoding length) and
/// valueLength must be non-zero.
/// </summary>
///     After calling: A TLV-encoded integer has been added to the output
/// <param name="value"> [in]: pointer to sequence of octets, most-significant byte first,
///                            which contains the integer value to encoded</param>
//  <param name="valueLength"> length of value (bytes)</param>
/// <param name="result">[in/out] input: pointer to pointer to next output location,
///                               output: point to pointer to next output location
///                               after the newly created TLV</param>
/// <param name="resultLength">[in/out] input: pointer to maximum number of remaining message bytes,
///                               output: pointer to maximum number of remaining message bytes after adding TLV</param>
void asn1_buildInteger(uint8_t* value, uint16_t valueLength, uint8_t** result, uint16_t* resultLength)
{		
    tlv_create(ASN1_TAG_INTEGER, value, valueLength, result, resultLength);    
}

/// <summary>
/// Encode: Set the tag and reserve a length field for a wrapping tag.
///
/// This function places the tag for the enclosing type in the output buffer and
/// reserves room for the length code, to be set by asn1_closeTag() or asn1_closeTag_BER().
///
///  For BER the constructed bit of the tag must be set.
/// </summary>
/// <returns>   Pointer to first byte of length field for specified tag </returns>
/// <param name="tag">Tag of the enclosing type(must be 1 byte long, ie long-form tag not supported)</param>
/// <param name="result">[in/out] input: pointer to pointer to next unused location in output buffer,
///                               output: pointer to pointer to next unused location in output buffer after length adjustment for closed TLV</param>
/// <param name="resultLength">[in/out] input: pointer to maximum number of remaining message bytes,
///                               output: pointer to maximum number of remaining message bytes after adding tag and reserved length</param>
uint8_t* asn1_openTag1(uint8_t tag, uint8_t** result, uint16_t* resultLength)
{
    uint8_t* lengthPosition;
    tlv_setTag(tag, result, resultLength);
    lengthPosition = *result;
    tlv_skip(1, result, resultLength);
    return lengthPosition; 
}

uint8_t* asn1_openTag2(uint8_t tag, uint8_t skipLength, uint8_t** result, uint16_t* resultLength)
{
    uint8_t* lengthPosition;
    tlv_setTag(tag, result, resultLength);
    lengthPosition = *result;
    tlv_skip(skipLength, result, resultLength);
    return lengthPosition; 
}

/// <summary>
/// Encode: Close the last-opened open wrapper type.
///
/// This function sets the length of the most-recently opened wrapper tag.
/// The result is BER-compliant.
/// </summary>
///     Prior calling: asn1_open() has been previously called for the current message.
///     After calling: the encoding for the previously opened tag is complete.
/// <param name="lengthPositionInResultBuffer">Return value of asn1_openTag() for the wrapped tag being closed</param>
/// <param name="result">[in/out] input: pointer onto target location buffer, output: point onto the first byte after the newly created TLV</param>
void asn1_closeTag1(uint8_t* lengthPositionInResultBuffer, uint8_t* result)
{
    uint16_t length = (uint16_t)(result - lengthPositionInResultBuffer);
    tlv_setLength(length - 1, &lengthPositionInResultBuffer, &length, TLV_ANY_LENGTH_SIZE);
}

void asn1_closeTag2(uint8_t lengthSize, uint8_t* lengthPositionInResultBuffer, uint8_t* result)
{
    uint16_t length = (uint16_t)(result - lengthPositionInResultBuffer);
    tlv_setLength(length - lengthSize, &lengthPositionInResultBuffer, &length, lengthSize);
}

/*
/// <summary>
/// Encode: Close the last-opened open wrapper type with DER-compliant length encoding.
///
/// This function sets the length of the most-recently opened wrapper tag.
/// The result uses minimum length encoding for the wrapped data, making
/// it BER and DER compliant.  The previous contents of the buffer are
/// shifted (and *result and *resultLength adjusted) if the length of the
/// wrapped item requires is one or two bytes.
/// </summary>
/// <param name="lengthPositionInResultBuffer">Return value of asn1_openTag() for the wrapped tag being closed</param>
/// <param name="result">[in/out] input: pointer to pointer to next unused location in output buffer,
///                               output: pointer to pointer to next unused location in output buffer after length adjustment for closed TLV</param>
/// <param name="resultLength">[in/out] input: pointer to maximum number of remaining message bytes,
///                               output: pointer to maximum number of remaining message bytes after a length adjustment for closed TLV</param>
void asn1_closeTag_DER(uint8_t* lengthPositionInResultBuffer, uint8_t** result, uint16_t* resultLength)
{
    uint8_t* wrappedTLVs;
    uint16_t lenlen;          
    uint16_t length;             

    // start of wrapped TLVs
    wrappedTLVs = lengthPositionInResultBuffer + 3;

    // length of the length code
    lenlen = 3;

    // length of wrapped data
    length = (uint16_t)(*result - lengthPositionInResultBuffer) - 3;

    // Set length and update ptr to next available location
    tlv_setLength(length, &lengthPositionInResultBuffer, &lenlen, TLV_ANY_LENGTH_SIZE);

    lenlen = 3 - lenlen;

    if (lenlen != 3) {
        // Move contents to start after length code
        utils_copy(wrappedTLVs, lengthPositionInResultBuffer, length);

        // Adjust next available location and remaining byte count
        *result -= (3 - lenlen);
        *resultLength += (3 - lenlen);
    }
}
	 
/// <summary>
/// Decode: Extract a single-byte BER-encoded integer as an
///         unsigned value.
///
/// Note: For negative values, this returns (value + 0x100).
/// </summary>
/// <param name="data">[in] pointer to first byte of a BER-encoded integer</param>
/// <param name="length">length of data field</param>
uint8_t asn1_integerAsU1(uint8_t* Data, uint16_t length)
{
    if (length > 1) {
        Throw(TLV_MALFORMED);
    }
    if (length == 0) {
        // Note: This is an extension to BER-encoding, since BER requires a length of 1 for value 0.
        return 0;
    }
    return Data[0];
}
  
/// <summary>
/// Decode: Extract an a one- or two-byte BER-encoded integer as an
///         unsigned value.
///
/// Note: For negative values, this returns the non-negative
///        (value + (1 << (length*8)).
/// </summary>
/// <param name="data">[in] pointer to first byte of a BER-encoded integer</param>
/// <param name="length">length of data field</param>
uint16_t asn1_integerAsU2(uint8_t* Data, uint16_t length)
{
    if (length > 2) {
        Throw(TLV_MALFORMED);
    }
    if (length == 1) {
        return asn1_integerAsU1(Data, length);
    }
    if (length == 0) {
        // Note: This is an extension to BER-encoding, since BER requires a length of 1 for value 0.
        return 0;
    }
    return utils_getU2(Data);
}

/// <summary>
/// Decode: Extract an a one- to four-byte BER-encoded integer as an
///         unsigned value.
///
/// Note: a TLV_MALFORMED exception is thrown for negative values.
///       
/// </summary>
/// <param name="data">[in] pointer to first byte of a BER-encoded integer</param>
/// <param name="length">length of data field</param>
uint32_t asn1_integerAsU4(uint8_t* Data, uint16_t length)
{    
    if (Data[0] & 0x80) {
        Throw(TLV_MALFORMED);
    }
    switch (length) {
        case 0:
            return 0;

        case 1:
            return asn1_integerAsU1(Data, length);            

        case 2:
            return asn1_integerAsU2(Data, length);

        case 3:
            {
                return (uint32_t)((Data[0] << 16) + utils_getU2(&Data[1]));                
            }

        case 4:
            return utils_getU4(Data);

        case 5:
            if (Data[0] != 0) {
                Throw(TLV_MALFORMED);
            }
            return utils_getU4(Data + 1);
			
        default:
            Throw(TLV_MALFORMED); 
            // to please compiler
            return 0;
    }
}
 
/// <summary>
/// Decode: Extract a boolean value.
/// </summary>
/// <returns>Boolean value (FALSE or TRUE) </returns>
/// <param name="data">[in] pointer to first byte of a BER-encoded BOOLEAN data field</param>
/// <param name="length">length of data field</param>
uint8_t asn1_booleanAsU1(uint8_t* Data, uint16_t length)
{
    if (length > 1) {
        Throw(TLV_MALFORMED);
    }
    //return ((Data[0] == 0) ? _FALSE : _TRUE);
	return ((Data[0] == 0) ? 0 : 1);
} 

/// <summary>
/// Compare 2 multibyte unsigned integers
///
/// This returns
/// +1 for int1 > int2, -1 for int1 < int2, 0 for int1 = int2
///
/// The unsigned values are stored in Big Endian order.
/// Buffers can be can be of different length.
/// Example: 000023FE compared to 0023FF will return -1.
/// </summary>
/// <param name="int1">[in] pointer to first integer</param>
/// <param name="int1Length">length of first integer</param>
/// <param name="int2">[in] pointer to second integer</param>
/// <param name="int2Length">length of second integer</param>
int8_t asn1_integerCompare(uint8_t* int1, uint8_t int1Length, uint8_t* int2, uint8_t int2Length)
{
	uint8_t rightMsbInt1;
	uint8_t rightMsbInt2;

	// first we check the right aligned MSB indexes
	rightMsbInt1 = int1Length - utils_msbIndex(int1, int1Length);
	rightMsbInt2 = int2Length - utils_msbIndex(int2, int2Length);	

	if (rightMsbInt1 > rightMsbInt2) {
		// int1 > int2
		return 1;		
	} else if (rightMsbInt1 < rightMsbInt2) {
		// int1 < int2
		return -1;		
	} else {
		uint8_t i;

		// right aligned MSB indexes are identical - we can normalize the buffers
		int1 = &int1[int1Length - rightMsbInt1];
		int2 = &int2[int2Length - rightMsbInt2];		

		// now perform byte to byte comparison
		for (i = 0; i < rightMsbInt1; i++) {
			if (int1[i] > int2[i]) {
				return 1;				
			} else if (int1[i] < int2[i]) {
				return -1;				
			}			
		}

		// numbers are identical
		return 0;
	}
}
	
/// <summary>
// BER-encode OID/ROID subidentifier
///
/// This encodes a subidentifier (except for first subidentifier of an OID).
/// </summary>
/// <param name="value"> [in] subidentifier to be encoded, MS byte first.
///                     First byte must be non-zero unless value=0.<\param>
/// <param name="valueLength"> length of subidentifier in significant bits <\param>
/// <param name="encodedValue"> [out ] pointer to BER encoding of value<\param>
/// <param name="encodedValueMaxLength"> maximum allowed length of encoded subidentifier<\param>
/// <returns>length of encoded value, bytes </returns>
uint16_t asn1_buildOIDValue(uint8_t* value, uint16_t valueLength, uint8_t* encodedValue, uint16_t encodedValueMaxLength)
{
	uint32_t carry;
	uint16_t encodedValueLength;
	int16_t i;
	int16_t j;

	encodedValueLength = OID_VALUE_LENGTH(valueLength);

	if (encodedValueLength > encodedValueMaxLength) {
		Throw(TLV_INSUFFICIENT_BUFFER);			
	}
	
	carry = 0;
    j = encodedValueLength - 1;

    for (i = valueLength - 1; i >= 0; i--, j--)
    {    
        uint8_t b;

        carry |= (uint32_t)(value[i] << (valueLength - i - 1));
        b = (uint8_t)(carry & 0x7F);
        if (i < valueLength - 1)
        {
            b |= 0x80;
        }
        encodedValue[j] = b;
        carry >>= 7;                 
    }

    for (; j >= 0; j--)
    {
        uint8_t b;
        b = (uint8_t)(carry & 0x7F);
        b |= 0x80;                
        encodedValue[j] = b;
        carry >>= 7;
    }
    
    return encodedValueLength;	
}
	*/
